#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
import os
import pwd
import re
import time
from stat import S_ISDIR, S_ISLNK, S_ISREG

from common.common import check_path_legal
from common.common import execute_cmd
from common.const import EnumPathType, CMDResult
from common.logger import Logger
from common.util.backup import backup_files, query_progress, backup
from generaldb.gbase8a import LOGGER
from generaldb.gbase8a.common.common_models import CreateSoftLink, ModifyPermission
from generaldb.gbase8a.common.const import FileBackToolRET
from generaldb.gbase8a.common.gbase_common import get_uid_gid

log = Logger().get_logger()


class FileOperate:
    def __init__(self):
        pass

    @staticmethod
    def change_path_permission(path_info: ModifyPermission):
        """
        修改路径权限包括文件和文件夹，不存在报错,
        :param path_info: 路径信息，需要传入 ModifyPermision 结构体
        :return: Bool
        """
        if not os.path.exists(path_info.path):
            return False
        if path_info.user:
            if not FileOperate.modify_path_user(path_info):
                return False
        if path_info.mode:
            if not FileOperate.modify_path_mode(path_info):
                return False
        return True

    @staticmethod
    def mkdir_change_permission(path_info: ModifyPermission):
        """
        创建指定权限的文件夹
        :param path_info: 路径信息，需要传入ModifyPermision结构体
        :return: Bool
        """
        if os.path.exists(path_info.path):
            log.error(f"The path already exists.{path_info.path}")
            return False
        else:
            try:
                os.mkdir(path_info.path)
            except Exception as exception_info:
                log.exception(f"Exception:{exception_info}")
                log.error("Failed to create the folder.")
                return False
        if not FileOperate.change_path_permission(path_info):
            log.error("Failed to modify the path permission.")
            return False
        return True

    @staticmethod
    def create_soft_link(path_info: CreateSoftLink):
        """
        创建软链接，创建前校验源路径用户,创建前请确保目标路径不存在
        :param path_info: 路径信息，需要传入 CreateSoftLink 实例
        :return: Bool
        """
        if not os.path.exists(path_info.src_path):
            log.error("The source path does not exist.")
            return False
        if os.path.exists(path_info.dst_path):
            log.error("The soft link destination path already exists.")
            return False
        try:
            os.symlink(path_info.src_path, path_info.dst_path)
        except Exception as exception_info:
            return False
        return True

    @staticmethod
    def modify_path_mode(path_info: ModifyPermission):
        """
        修改路径权限
        :param path_info: 路径信息，需要传入 ModifyPermision 结构体
        :return: Bool
        """
        if path_info.mode:
            try:
                os.lchmod(path_info.path, path_info.mode)
            except Exception as e:
                log.error(f"Change dest path mode failed, exception: {e}")
                return False
        return True

    @staticmethod
    def modify_path_user(path_info: ModifyPermission):
        """
        修改路径用户
        :param path_info: 路径信息，需要传入ModifyPermision结构体
        :return: Bool
        """
        if path_info.user == 'root':
            return False
        uid, gid = get_uid_gid(path_info.user)
        if not uid:
            log.error("User is invalid can not change permission")
            return False
        try:
            os.lchown(path_info.path, uid, gid)
        except Exception as e:
            log.error(f"Change dest path owner failed, exception: {e}")
            return False
        return True

    @staticmethod
    def read_json_file(file_path):
        if not os.path.isfile(file_path):
            raise Exception("File not exist")
        try:
            with open(file_path, "r", encoding='UTF-8') as file_obj:
                json_dict = json.loads(file_obj.read())
        except Exception as exception:
            raise Exception("parse param file failed") from exception
        return json_dict

    @staticmethod
    def check_path_validity(path):
        """
        检查路径是否含有特殊字符
        :param path:
        :return:
        """
        # 不能为空，不能含有?*|;&$><`"\\!.等字符
        regex = r'^[^?*|;&$><`"\\!.]+$'
        if not re.match(regex, path):
            log.error(f"Path: {path} is invalid")
            return False
        return True

    @staticmethod
    def get_file_type(path):
        """
        获取路径类型，文件 文件夹 符号链接
        :param path:
        :return:
        """
        try:
            status = os.lstat(path).st_mode
        except Exception as e:
            log.error(f"Get path info failed, exception: {e}")
            return EnumPathType.INVALID_TYPE
        if S_ISLNK(status):
            return EnumPathType.LINK_TYPE
        elif S_ISDIR(status):
            return EnumPathType.DIR_TYPE
        elif S_ISREG(status):
            return EnumPathType.FILE_TYPE
        log.error(f"Path not a valid type: {status}")
        return EnumPathType.INVALID_TYPE

    @staticmethod
    def recursive_modification_permission(path, parent_dir, user=None, mode=None):
        """
        递归修改路径下所有文件权限, 需要调用者保证路径安全
        :param path: 路径信息
        :param user: 用户信息
        :param mode: 权限
        :return: Bool
        """
        ret = check_path_legal(path, parent_dir)
        if not ret:
            log.error("Check path legal fail")
            return False
        if not os.path.exists(path):
            log.error("The path does not exist.")
            return False
        if user:
            try:
                uid = pwd.getpwnam(str(user)).pw_uid
            except KeyError:
                log.error('The specified user does not exist.')
                return False
            group_id = pwd.getpwnam(str(user)).pw_gid
            cmd = 'chown {}:{} -R {}'.format(uid, group_id, path)
            LOGGER.info("Change permission:%s", cmd)
            ret, out, err = execute_cmd(cmd)
            if ret != CMDResult.SUCCESS:
                LOGGER.error("Failed to modify the user, out:%s, err:%s", out, err)
            else:
                LOGGER.info("Execute cmd success. cmd:%s, out:%s", cmd, out)
        if mode:
            cmd = 'chmod {} -R {}'.format(mode, path)
            ret, out, err = execute_cmd(cmd)
            if ret != CMDResult.SUCCESS:
                LOGGER.error("Failed to modify the mode, out:%s, err:%s", out, err)
                return False
        return True

    @staticmethod
    def cp_file_list(job_id, file_list, target_path):
        res = backup_files(job_id, file_list, target_path)
        if not res:
            LOGGER.error(f"Failed to start copy file. jobId: {job_id}.")
            return False
        return FileOperate.get_cp_result(job_id)

    @staticmethod
    def cp_dir(job_id, source_dir, target_dir):
        res = backup(job_id, source_dir, target_dir)
        if not res:
            LOGGER.error(f"Failed to start copy directory, jobId: {job_id}.")
            return False
        return FileOperate.get_cp_result(job_id)

    @staticmethod
    def get_cp_result(job_id):
        interval = 0
        while True:
            interval += 1
            time.sleep(2)
            status, progress, data_size = query_progress(job_id)
            if status == FileBackToolRET.SUCCESS:
                LOGGER.info(f"Copy completed, jobId: {job_id}.")
                return True
            if status == FileBackToolRET.FAILED:
                LOGGER.error(f"Copy failed, jobId: {job_id}.")
                return False
